﻿using SharpDepend.Datatypes;
using SoundEngine3;
using SoundEngine3.AudioProcessing;
using SoundEngine3.DspPlugins;
using SoundEngine3.DspPlugins.Meta;
using SoundEngine3.MIDI.Meta;
using System;
using System.Collections.Generic;
using System.IO;
using ZquenceStudio3.Core;
using ZquenceStudio3.Core.DspPlugins;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Modules.Container;
using ZquenceStudio3.Core.Storage;
using ZquenceStudio3.Core.Utils;
using SharpDepend.Storage.DataFormats;
using SharpDepend.Storage;

namespace ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer
{
    public interface IModulizerUI
    {
        void OnAddedModule(BaseModule module);

        void OnRemovedModule(BaseModule module);

        void RebuildSynthGraph(IModuleContainer moduleContainer);

        void SetCustomData(CustomNode custom);

        void GetCustomData(CustomNode customData);
    }

    /// <summary>
    /// Buildable synthesizer with nodes as modules with input, output and parameters.
    /// </summary>
    public abstract class BaseModulizer : CoreDspPlugin
    {
        internal readonly ModuleManager mModuleManager;

        private static int sParameterIDNext;

        private BaseAudioBuffer m_internal_output;
        private IModulizerUI mUI;
        private List<ModuleParameter> mAllParameters = new List<ModuleParameter>();
        private bool _using_edata;

        public BaseModulizer(ZquenceStudio3Context coreContext, DspPluginInfo dspPlugin, ModuleManager.ModulizerType type)
            : base(coreContext, dspPlugin)
        {
            // Parameters handels the IDs by another UniqueParameterManager. So disable this.
            UniqueParameters.Enable = false;

            mModuleManager = new ModuleManager(coreContext.SoundEngine.SoundEngineContext, this, type, new SoundEngine3.Internal.UniqueIdentityManager<ParameterProperties>());
            mModuleManager.OnAddedModule += OnAddedModule;
            mModuleManager.OnRemovedModule += OnRemovedModule;

            CreateDefaultBuilder();
            
            m_internal_output = new BaseAudioBuffer(this);

            ReCreateBuffer();

            SupportAudioProcessing = true;
        }

        internal void SetUI(IModulizerUI ui)
        {
            mUI = ui;
        }

        protected abstract void CreateDefaultBuilder();
        
        /// <summary>
        /// Updates all parameters and remove them parameters that is internally set or connected to a another module.
        /// </summary>
        internal void UpdateParameterStates()
        {
            for (int i = 0; i < mAllParameters.Count; i++)
            {
                var parameter = mAllParameters[i];
                
                // If parameter is valid to be used.
                if (IsParameterValid(parameter))
                {
                    // And if it doesnt exist in list. Add it!
                    if (!ContainsParameter(parameter))
                    {
                        // These parameters can be reused.
                        parameter.Reused();

                        AddParameter(parameter);
                    }
                }
                else
                {
                    if (ContainsParameter(parameter))
                    {
                        // Otherwise remove it from list.
                        RemoveParameter(parameter);
                    }
                }
            }
        }

        private bool IsParameterValid(ModuleParameter parameter)
        {
            if (parameter.InternallyVisible)
            {
                return false;
            }
            else if (parameter is InputModuleParameter)
            {
                // If parameter is connected to another module, then make it not visible to use.
                InputModuleParameter inputParam = (InputModuleParameter)parameter;
                if (inputParam.InternalInputModule.IsConnected)
                {
                    return false;
                }
            }
            return true;
        }

        void OnAddedModule(BaseModule module)
        {
            if (!(module is GroupModule))
            {
                foreach (var parameter in module.Parameters)
                {
                    // Generate a parameter ID.
                    if (parameter.ID == -1)
                    {
                        (parameter as SoundEngine3.Internal.IUniqueIdentity).SetId(sParameterIDNext++);
                    }

                    // These parameters can be reused.
                    parameter.Reused();

                    AddParameter(parameter);

                    var param = parameter as ModuleParameter;
                    if (param == null)
                    {
                        throw new Exception("Parameter must be a class of ModuleParameter");
                    }

                    mAllParameters.Add(parameter as ModuleParameter);
                }
            }

            mUI?.OnAddedModule(module);
        }

        void OnRemovedModule(BaseModule module)
        {
            if (!(module is GroupModule))
            {
                foreach (var parameter in module.Parameters)
                {
                    // Clear parameter ID.
                    if (parameter.ID != -1)
                    {
                        (parameter as SoundEngine3.Internal.IUniqueIdentity).SetId(-1);
                    }

                    if (ContainsParameter(parameter))
                    {
                        RemoveParameter(parameter);
                    }

                    mAllParameters.Remove(parameter as ModuleParameter);
                }
            }

            mUI?.OnRemovedModule(module);
        }

        #region Synthesizer settings

        private int mLastBufferSize;
        
        public override string PluginInfo
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public override Version Version
        {
            get
            {
                return new Version(1, 0);
            }
        }

        protected override void OnStoped()
        {
            foreach (var module in mModuleManager.Modules)
            {
                module.OnStoped();
            }
        }

        public override void ChangeBufferSize(int bufferSize)
        {
            mLastBufferSize = bufferSize;

            foreach (var channel in m_internal_output.Channels)
            {
                channel.SetBufferSize(bufferSize);
            }

            // Set buffer size on all loaded modules.
            foreach (var module in mModuleManager.Modules)
            {
                module.SetBufferSize(bufferSize);
            }

            // Set buffer size on all loaded modules.
            foreach (var module in mModuleManager.Modules)
            {
                module.SetBufferSizeChanged(bufferSize);
            }
        }

        public override void ChangeInputChannels(int channels)
        {
            foreach (var module in mModuleManager.Modules)
            {
                module.ChangeInputChannels(channels);
            }
        }

        public override void ChangeOutputChannels(int channels)
        {
            ReCreateBuffer();

            foreach (var module in mModuleManager.Modules)
            {
                module.ChangeOutputChannels(channels);
            }
        }

        private void ReCreateBuffer()
        {
            int bufferSize = SoundEngineContext.BufferSize;
            int outputChannels = SoundEngineContext.OutputChannels;

            m_internal_output.SetChannels(outputChannels, bufferSize);
        }

        public override void ChangeSampleRate(double sampleRate)
        {
            m_internal_output.SampleRate = sampleRate;

            foreach (var module in mModuleManager.Modules)
            {
                module.ChangeSampleRate(sampleRate);
            }
        }
        
        public override void Process(BaseAudioBuffer input, BaseAudioBuffer output)
        {
            throw new NotSupportedException("Process metod can not be used for Nodesizer.");
        }

        protected override void OnProcessEvents(List<MidiMessage> midiMessages)
        {
            mModuleManager.ProcessEvents(midiMessages);
        }

        public override BaseAudioBuffer OnProcessOutput()
        {
            mModuleManager.InterateProcess();

            double[] samples1 = m_internal_output.Channels[0].Samples;
            double[] samples2 = m_internal_output.Channels[1].Samples;
            Array.Copy(mModuleManager.Out.ProcessedLeft, samples1, mModuleManager.Out.ProcessedLeft.Length);
            Array.Copy(mModuleManager.Out.ProcessedRight, samples2, mModuleManager.Out.ProcessedRight.Length);

            return m_internal_output;
        }

        #endregion

        #region Quick save.

        [Obsolete("This will be removed in the stable release")]
        public override int DataCount
        {
            get
            {
                return 1;
            }
        }

        public override void SaveData(CustomNode node)
        {
            CustomNode sub = new CustomNode(node);

            lock (SoundEngine3.SoundEngine.SoundLocker)
            {
                SubPackData(sub, mModuleManager);
            }

            // Save custom data.
            CustomNode customData = new CustomNode(sub);
            customData.Name = "_Custom";
            mUI?.GetCustomData(customData);
        }

        public override void LoadData(CustomNode fromNode)
        {
            CustomNode sub = fromNode;

            if (sub.Name == "EData")
            {
                sub = sub.Get("ModuleManager");
                _using_edata = true;
            }

            // Clear and load.
            lock (SoundEngine3.SoundEngine.SoundLocker)
            {
                ClearManager();

                // Get ModuleManager.
                UnpackDataToAdd(sub, mModuleManager);

                // Set custom data.
                CustomNode custom = sub.Get("_Custom");
                if (custom != null)
                {
                    mUI?.SetCustomData(custom);
                }

                mUI?.RebuildSynthGraph(mModuleManager);
            }
        }

        [Obsolete("This will be removed in the stable release")]
        public override void SetData(int index, string data)
        {
            if (index != 0)
            {
                throw new IndexOutOfRangeException(GetType().Name + " only support data index at 0.");
            }

            byte[] input = HexConverter.FromHex(data);

            CustomNode node = new CustomNode();
            using (MemoryStream stream = new MemoryStream())
            {
                stream.Write(input, 0, input.Length);
                node.SetData(new SimpleC2(), stream);
            }

            LoadData(node);
        }
        
        internal new void RemoveParameter(ParameterProperties param)
        {
            base.RemoveParameter(param);
        }
        
        internal bool ContainsParameter(ParameterProperties param)
        {
            return base.Parameters.Contains(param);
        }

        private static void SubPackData(CustomNode node, IModuleContainer fromModule)
        {
            // Pack sub modules.
            CustomNode nodeSub = new CustomNode(node);
            nodeSub.Name = "Sub";
            foreach (var subModule in fromModule.SubModules)
            {
                CustomNode nodeSubModule = new CustomNode(nodeSub);

                if (subModule is IModuleContainer)
                {
                    SubPackData(nodeSubModule, subModule as IModuleContainer);
                }
                else
                {
                    PackModule(nodeSubModule, subModule);
                }
            }

            PackModule(node, (BaseModule)fromModule);
        }
    
        public void RemoveModule(BaseModule baseModule)
        {
            if (!mModuleManager.IsProtectedModule(baseModule))
            {
                lock (SoundEngine3.SoundEngine.SoundLocker)
                {
                    GroupModule parent = baseModule.ParentModule as GroupModule;

                    parent.RemoveSubModule(baseModule);

                    mUI?.RebuildSynthGraph(parent);
                }
            }
        }

        private static void PackModule(CustomNode nodeModule, BaseModule module)
        {
            nodeModule.Name = module.GetType().Name;
            nodeModule.AddProperty("ModuleID", module.ID);
            nodeModule.AddProperty("ModuleName", module.Name);

            nodeModule.AddProperty("X", module.GuiPosition.X);
            nodeModule.AddProperty("Y", module.GuiPosition.Y);
            nodeModule.AddProperty("width", module.GuiPosition.Width);
            nodeModule.AddProperty("height", module.GuiPosition.Height);

            // Set custom module data.
            CustomNode moduleData = new CustomNode(nodeModule);
            moduleData.Name = "ModuleData";
            module.SetData(moduleData);

            // Loop all outputs.
            CustomNode nodeOutputs = new CustomNode(nodeModule);
            nodeOutputs.Name = "Outputs";
            foreach (var output in module.Outputs)
            {
                nodeOutputs.AddProperty("Output", output.ID);

                if (output.IsConnected)
                {
                    CustomNode nodeOutput = new CustomNode(nodeOutputs);
                    nodeOutput.Name = output.Name;

                    nodeOutput.AddProperty("Output", output.ID.ToString());

                    // Connected inputs.
                    foreach (var input in output.ConnectedInputs)
                    {
                        CustomNode nodeInput = new CustomNode(nodeOutput);
                        nodeInput.Name = "Input";

                        nodeInput.AddProperty("ToModule", input.Module.ID.ToString());
                        nodeInput.AddProperty("ToInput", input.ID.ToString());
                    }
                }
            }
            CustomNode nodeOutputNames = new CustomNode(nodeModule);
            nodeOutputNames.Name = "OutputNames";
            foreach (var output in module.Outputs)
            {
                nodeOutputNames.AddProperty("OutputName", output.Name);
            }

            CustomNode nodeOutputInternally = new CustomNode(nodeModule);
            nodeOutputInternally.Name = "OutputInternallys";
            foreach (var output in module.Outputs)
            {
                nodeOutputInternally.AddProperty("OutputInternally", output.InternallyVisible);
            }

            // Loop all inputs to store their id's.
            CustomNode nodeInputs = new CustomNode(nodeModule);
            nodeInputs.Name = "Inputs";
            foreach (var input in module.Inputs)
            {
                nodeInputs.AddProperty("Input", input.ID);
            }
            CustomNode nodeInputNames = new CustomNode(nodeModule);
            nodeInputNames.Name = "InputNames";
            foreach (var input in module.Inputs)
            {
                nodeInputNames.AddProperty("InputName", input.Name);
            }
            CustomNode nodeInputInternallys = new CustomNode(nodeModule);
            nodeInputInternallys.Name = "InputInternallys";
            foreach (var input in module.Inputs)
            {
                nodeInputInternallys.AddProperty("InputInternally", input.InternallyVisible);
            }

            // Loop all parameters.
            CustomNode nodeParameters = new CustomNode(nodeModule);
            nodeParameters.Name = "Parameters";
            foreach (ModuleParameter parameter in module.Parameters)
            {
                CustomNode nodeParameter = new CustomNode(nodeParameters);
                nodeParameter.Name = parameter.Label;

                nodeParameter.AddProperty("Index", parameter.ID.ToString());
                nodeParameter.AddProperty("Value", parameter.RawValue.ToString());
                nodeParameter.AddProperty("Min", parameter.MinValue.ToString());
                nodeParameter.AddProperty("Max", parameter.MaxValue.ToString());
                nodeParameter.AddProperty("Internally", parameter.InternallyVisible.ToString());
            }
        }

        public void SaveTemplate(CustomNode fileNode, BaseModule module)
        {
            lock (SoundEngine3.SoundEngine.SoundLocker)
            {
                if (module is IModuleContainer)
                {
                    // Save module and sub modules.
                    SubPackData(fileNode, module as IModuleContainer);
                }
                else
                {
                    // Save only module.
                    PackModule(fileNode, module);
                }
            }
        }

        public void AddTemplate(CustomNode fileNode, IModuleContainer inModuleContainer)
        {
            UnpackDataToAdd(fileNode, inModuleContainer);
        }

        /// <summary>
        /// Unpack a data module from file to add to current module.
        /// </summary>
        /// <param name="node"></param>
        public void UnpackDataToAdd(CustomNode file, IModuleContainer inModuleContainer)
        {
            // Maps the moduleId from the file to what the modules will actually get when it adds here.
            Dictionary<int, BaseModule> moduleIdMapping = new Dictionary<int, BaseModule>();

            lock (SoundEngine3.SoundEngine.SoundLocker)
            {
                // Add and load.
                //ClearManager();
                //UnpackData(node);
                Dictionary<BaseModule, CustomNode> loadOutputLast = new Dictionary<BaseModule, CustomNode>();
                Dictionary<KeyValuePair<int, int>, ModuleInput> routeInputId = new Dictionary<KeyValuePair<int, int>, ModuleInput>();
                Dictionary<KeyValuePair<int, int>, ModuleOutput> routeOutputId = new Dictionary<KeyValuePair<int, int>, ModuleOutput>();
                _UnpackDataToAdd(file, inModuleContainer, moduleIdMapping, routeInputId, routeOutputId, loadOutputLast);

                foreach (var item in loadOutputLast)
                {
                    BaseModule module = item.Key;
                    CustomNode node = item.Value;

                    // JavaScript style, usch.
                    int moduleID = node.GetPropertyValue<int>("__ModuleID");

                    // Loop all outputs.
                    CustomNode nodeOutputs = node.Get("Outputs");
                    foreach (var nodeOutput in nodeOutputs.Nodes)
                    {
                        string rawValue = nodeOutput.GetPropertyValue("Output");

                        if (rawValue != null)
                        {
                            int outoutId = nodeOutput.GetPropertyValue<int>("Output");

                            // Loop connected inputs.
                            foreach (var nodeInput in nodeOutput.Nodes)
                            {
                                int toModuleId = nodeInput.GetPropertyValue<int>("ToModule");
                                int inputId = nodeInput.GetPropertyValue<int>("ToInput");

                                if (!moduleIdMapping.ContainsKey(toModuleId))
                                {
                                    // This module is outside the container module. Skip it.
                                    continue;
                                }

                                // Route module id.
                                //BaseModule toModule = mModuleManager.Find(routedToModuleId);
                                BaseModule toModule = moduleIdMapping[toModuleId];

                                ModuleInput toInput = toModule.GetInput(inputId);
                                var key = new KeyValuePair<int, int>(toModuleId, inputId);
                                if (routeInputId.ContainsKey(key))
                                {
                                    if (toInput != null)
                                    {
                                        // NOTE: Problably right, but this means that we got another wrong
                                        // toInput? Anyway route it to the right toInput instead.
                                    }
                                    toInput = routeInputId[key];
                                }

                                ModuleOutput output = module.GetOutput(outoutId);
                                key = new KeyValuePair<int, int>(moduleID, outoutId);
                                if (routeOutputId.ContainsKey(key))
                                {
                                    if (output != null)
                                    {
                                        // NOTE: Problably right, but this means that we got another wrong
                                        // toInput? Anyway route it to the right toInput instead.
                                    }
                                    output = routeOutputId[key];
                                }
                                
                                if (output == null || output.ConnectedInputs.Contains(toInput))
                                {
                                    // Already added. Then this try was when a GroupModule try to connect.
                                    // But the moduleIO is duplicated from a inner module. Skip it.
                                    continue;
                                }

                                try
                                {
                                    output.AddInput(toInput);
                                }
                                catch (Exception error)
                                {
                                    AddError(error.Message);
                                }
                            }
                        }
                    }
                    // Loop all outputs.
                    CustomNode nodeOutputNames = node.Get("OutputNames");
                    for (int i = 0; i < nodeOutputNames.Properties.Count; i++)
                    {
                        var name = nodeOutputNames.Properties[i].Value;

                        if (i >= module.Outputs.Count)
                        {
                            AddError("Module output '" + name + "' to index " + i + " do not exist.");
                        }
                        else
                        {
                            module.Outputs[i].Name = name;
                        }
                    }
                    // Loop all outputs.
                    CustomNode nodeOutputInternally = node.Get("OutputInternallys");
                    if (nodeOutputInternally != null)
                    {
                        for (int i = 0; i < module.Outputs.Count; i++)
                        {
                            if (i < nodeOutputInternally.Properties.Count)
                            {
                                var internally = nodeOutputInternally.Properties[i].GetValue<bool>();

                                module.Outputs[i].InternallyVisible = internally;
                            }
                            else
                            {
                                // New output that do not exist in file. Set it to internally.
                                module.Outputs[i].InternallyVisible = true;
                            }
                        }
                    }
                    // Loop all inputs.
                    CustomNode nodeInputNames = node.Get("InputNames");
                    for (int i = 0; i < nodeInputNames.Properties.Count; i++)
                    {
                        string name = nodeInputNames.Properties[i].Value;

                        if (i >= module.Inputs.Count)
                        {
                            AddError("Module input '" + name + "' to index " + i + " do not exist.");
                        }
                        else
                        {
                            module.Inputs[i].Name = name;
                        }
                    }
                    // Loop all input internally.
                    CustomNode nodeInputInternallys = node.Get("InputInternallys");
                    if (nodeInputInternallys != null)
                    {
                        for (int i = 0; i < module.Inputs.Count; i++)
                        {
                            if (i < nodeInputInternallys.Properties.Count)
                            {
                                var internally = nodeInputInternallys.Properties[i].GetValue<bool>();

                                module.Inputs[i].InternallyVisible = internally;
                            }
                            else
                            {
                                // New input that do not exist in file. Set it to internally.
                                module.Inputs[i].InternallyVisible = true;
                            }
                        }
                    }

                    // Loop all parameters.
                    CustomNode nodeParameters = node.Get("Parameters");
                    for (int i = 0; i < module.Parameters.Count; i++)
                    {
                        if (i >= nodeParameters.Nodes.Count)
                        {
                            //AddError("Parameter " + module.Parameters[i].Label + " is not saven in file. Will be ignored.");
                            (module.Parameters[i] as InputModuleParameter).InternallyVisible = true;
                        }
                        else
                        {
                            CustomNode parameterNode = nodeParameters.Nodes[i];
                            ModuleParameter property = (ModuleParameter)module.Parameters[i];

                            int index = parameterNode.GetPropertyValue<int>("Index", property.ID);
                            if (index == -1)
                            {
                                index = property.ID;
                            }
                            double value = parameterNode.GetPropertyValue<double>("Value");
                            double min = parameterNode.GetPropertyValue<double>("Min", property.MinValue);
                            double max = parameterNode.GetPropertyValue<double>("Max", property.MaxValue);
                            bool internally = parameterNode.GetPropertyValue<bool>("Internally", property.InternallyVisible);

                            if (value < min)
                            {
                                value = min;
                            }
                            else if (value > max)
                            {
                                value = max;
                            }

                            //property.Module.ChangeParameterId(property, index);
                            (property as SoundEngine3.Internal.IUniqueIdentity).SetId(index);

                            property.RawValue = value;
                            property.MinValue = min;
                            property.MaxValue = max;
                            property.Label = parameterNode.Name;
                            property.InternallyVisible = internally;
                        }
                    }

                    // Get custom module data.
                    CustomNode moduleData = node.Get("ModuleData");
                    if (moduleData != null)
                    {
                        module.GetData(moduleData);
                    }
                }

                UpdateParameterStates();
                mUI?.RebuildSynthGraph(inModuleContainer);

                OnUnpacked();
            }

            ChangeBufferSize(mLastBufferSize);
        }

        protected abstract void OnUnpacked();

        private void _UnpackDataToAdd(CustomNode file, IModuleContainer toContainerModule,
            Dictionary<int, BaseModule> moduleIdMapping,
            Dictionary<KeyValuePair<int, int>, ModuleInput> routeInputId, Dictionary<KeyValuePair<int, int>, ModuleOutput> routeOutputId,
            Dictionary<BaseModule, CustomNode> loadOutputLast)
        {
            // Unpack sub modules first.
            CustomNode nodeSub = file.Get("Sub");
            IModuleContainer nextContainerModule = null;

            // Do not touch Sub node.
            if (!file.Name.Equals("Sub"))
            {
                int moduleID = file.GetPropertyValue<int>("ModuleID");
                string moduleName = file.GetPropertyValue<string>("ModuleName", null);
                BaseModule module_ = null;

                if (file.Name.Equals("MidiInModule"))
                {
                    module_ = mModuleManager.MidiInput;
                }
                else if (file.Name.Equals("InModule"))
                {
                    module_ = mModuleManager.AudioInput;
                }
                else if (file.Name.Equals("ModuleManager"))
                {
                    module_ = mModuleManager;
                }
                else if (file.Name.Equals("OutModule"))
                {
                    module_ = mModuleManager.Out;
                }
                else
                {
                    module_ = ModulesRegistration.CreateInstance(file.Name, mModuleManager);
                    toContainerModule.AddSubModule(module_);
                }

                if (moduleName != null)
                {
                    module_.Name = moduleName;
                }

                // If sub node exist.
                if (nodeSub != null)
                {
                    if (nextContainerModule != null)
                    {
                        throw new Exception("Varför finns det två moduler som kontainermodul?");
                    }
                    nextContainerModule = (IModuleContainer)module_;
                }

                moduleIdMapping.Add(moduleID, module_);

                // Load next sub modules if available.
                if (nodeSub != null)
                {
                    if (nextContainerModule == null)
                    {
                        throw new Exception("Varför är nextContainerModule null när nodeSub inte är det?");
                    }
                    foreach (var subModuleNode in nodeSub.Nodes)
                    {
                        _UnpackDataToAdd(subModuleNode, nextContainerModule, moduleIdMapping, routeInputId, routeOutputId, loadOutputLast);
                    }
                }
                
                int moduleId = file.GetPropertyValue<int>("ModuleID");

                // Route module id.
                BaseModule module = moduleIdMapping[moduleId];

                double x = file.GetPropertyValue<double>("X");
                double y = file.GetPropertyValue<double>("Y");
                double width = file.GetPropertyValue<double>("width", 1);
                double height = file.GetPropertyValue<double>("height", 1);

                module.GuiPosition = new Vector4(x, y, x + width, y + height);

                // Set inputs ids.
                CustomNode inputs = file.Get("Inputs");
                for (int i = 0; i < inputs.Properties.Count; i++)
                {
                    Property input = inputs.Properties[i];

                    int inputID = input.GetValue<int>();

                    // Reset module input id.
                    if (i >= module.Inputs.Count)
                    {
                        AddError("Module input " + inputID + " do not exist and can not be used.");
                    }
                    else
                    {
                        module.Inputs[i].ID = inputID; //Alex.

                        routeInputId.Add(new KeyValuePair<int, int>(moduleId, inputID), module.Inputs[i]);
                    }
                }

                // Set outputs ids.
                CustomNode outputs = file.Get("Outputs");
                for (int i = 0; i < outputs.Properties.Count; i++)
                {
                    Property output = outputs.Properties[i];

                    int outputID = output.GetValue<int>();

                    // Reset module input id.
                    if (i >= module.Outputs.Count)
                    {
                        AddError("Module output " + outputID + " do not exist and can not be used.");
                    }
                    else
                    {
                        module.Outputs[i].ID = outputID; //Alex.

                        routeOutputId.Add(new KeyValuePair<int, int>(moduleId, outputID), module.Outputs[i]);
                    }
                }

                // Hack. Like a tag adding.
                // JavaScript style, usch.
                file.AddProperty("__ModuleID", moduleId);

                loadOutputLast.Add(module, file);
            }
        }
        
        public void ClearManager()
        {
            // Remove all modules.
            mModuleManager.ClearAllModules();
        }

        #endregion
    }
}
